package vip.liux.contracts.security.externalLogin;

import org.springframework.context.ApplicationContext;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.*;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import vip.liux.contracts.models.identity.UserDomainManager;
import vip.liux.contracts.security.sms.SmsAuthenticationConfig;

import java.util.Optional;

/**
 * 外部登录认证自定义配置器。
 * 支持对接第三方登录流程，自动注册所有 ExternalLoginAuthenticator 实现，便于扩展。
 */
public class ExternalLoginAuthenticationConfig extends AbstractHttpConfigurer<SmsAuthenticationConfig, HttpSecurity> {

    private UserDomainManager userService;

    private PasswordEncoder passwordEncoder;

    private AuthenticationSuccessHandler successHandler;

    private AuthenticationFailureHandler failureHandler;

    private String loginProviderParameter = "loginProvider";

    private String providerKeyParameter = "providerKey";

    /**
     * 用于匹配需要进行外部登录的请求
     * 默认匹配所有 POST /login/sms 请求
     */
    private RequestMatcher requiresAuthenticationRequestMatcher = new AntPathRequestMatcher("/login/sms", "POST");

    /**
     * 工厂方法，风格与 httpBasic() 一致
     */
    public static ExternalLoginAuthenticationConfig externalLoginAuthentication() {
        return new ExternalLoginAuthenticationConfig();
    }

    /**
     * 显式设置用户服务
     */
    public ExternalLoginAuthenticationConfig userService(UserDomainManager userService) {
        this.userService = userService;
        return this;
    }

    public ExternalLoginAuthenticationConfig successHandler(AuthenticationSuccessHandler handler) {
        this.successHandler = handler;
        return this;
    }

    public ExternalLoginAuthenticationConfig failureHandler(AuthenticationFailureHandler handler) {
        this.failureHandler = handler;
        return this;
    }

    public ExternalLoginAuthenticationConfig loginProcessingUrl(String loginProcessingUrl) {
        this.requiresAuthenticationRequestMatcher = new AntPathRequestMatcher(loginProcessingUrl, "POST");
        return this;
    }

    public ExternalLoginAuthenticationConfig requiresAuthenticationRequestMatcher(RequestMatcher requestMatcher) {
        Assert.notNull(requestMatcher, "requestMatcher cannot be null");
        this.requiresAuthenticationRequestMatcher = requestMatcher;
        return this;
    }

    public ExternalLoginAuthenticationConfig failureForwardUrl(String forwardUrl) {
        failureHandler(new ForwardAuthenticationFailureHandler(forwardUrl));
        return this;
    }

    public ExternalLoginAuthenticationConfig successForwardUrl(String forwardUrl) {
        successHandler(new ForwardAuthenticationSuccessHandler(forwardUrl));
        return this;
    }

    public ExternalLoginAuthenticationConfig loginProviderParameter(String loginProviderParameter) {
        this.loginProviderParameter = loginProviderParameter;
        return this;
    }

    public ExternalLoginAuthenticationConfig providerKeyParameter(String providerKeyParameter) {
        this.providerKeyParameter = providerKeyParameter;
        return this;
    }

    /**
     * 在 configure() 之前、安全配置链初始化的早期。
     * 用于注册AuthenticationProvider、UserDetailsService、全局共享对象等，这些对象在整个安全链中可能会被其它 configurer 复用
     */
    @Override
    public void init(HttpSecurity http) throws Exception {
        UserDomainManager actualUserService = Optional.ofNullable(this.userService)
                .orElseGet(() -> getBeanOrNull(http, UserDomainManager.class));
        PasswordEncoder passwordEncoder = Optional.ofNullable(this.passwordEncoder)
                .orElseGet(() -> getBeanOrNull(http, PasswordEncoder.class));
        UserCache userCache = getBeanOrNull(http, UserCache.class);

        Assert.notNull(actualUserService, "UserDetailsManager (userService) is required. Please configure it.");
        Assert.notNull(passwordEncoder, "PasswordEncoder is required. Please configure it.");

        // 持久化到成员变量，供 configure 及其它场景使用
        this.userService = actualUserService;
        this.passwordEncoder = passwordEncoder;

        ExternalLoginAuthenticationProvider provider = new ExternalLoginAuthenticationProvider();
        provider.setUserDomainManager(actualUserService);
        provider.setPasswordEncoder(passwordEncoder);
        if (userCache != null) {
            provider.setUserCache(userCache);
        }

        // 自动注入Spring ApplicationContext，用于认证器自动注册
        provider.setApplicationContext(http.getSharedObject(ApplicationContext.class));
        provider.afterPropertiesSet();

        http.authenticationProvider(provider);
    }

    /**
     * 在 所有 init() 调用后，配置链构建的后期。
     * 用于添加/排序 Filter、设置拦截规则、指定端点、定制 FilterChain。
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        ExternalLoginAuthenticationFilter filter = new ExternalLoginAuthenticationFilter();
        AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);

        filter.setAuthenticationManager(authenticationManager);
        // 设置前端参数名
        filter.setLoginProviderParameter(loginProviderParameter);
        filter.setProviderKeyParameter(providerKeyParameter);

        filter.setRequiresAuthenticationRequestMatcher(requiresAuthenticationRequestMatcher);

        // 设置成功/失败处理器
        if (this.successHandler != null) {
            filter.setAuthenticationSuccessHandler(this.successHandler);
        }
        if (this.failureHandler != null) {
            filter.setAuthenticationFailureHandler(this.failureHandler);
        }

        http.addFilterAfter(filter, UsernamePasswordAuthenticationFilter.class);
    }

    /**
     * 工具方法：从 ApplicationContext 获取 Bean，获取不到则返回 null
     */
    private <C> C getBeanOrNull(HttpSecurity http, Class<C> clazz) {
        ApplicationContext context = http.getSharedObject(ApplicationContext.class);
        if (context != null) {
            try {
                return context.getBean(clazz);
            } catch (Exception ignored) {
            }
        }
        return null;
    }
}